package ru.susu.algebra.chartable.constructor;

import java.math.BigInteger;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

import ru.susu.algebra.chartable.degree.HooksDegreeCalculator;
import ru.susu.algebra.field.quadratic.QuadraticField;
import ru.susu.algebra.field.quadratic.QuadraticFieldGeneralForm;
import ru.susu.algebra.field.quadratic.QuadraticInteger;
import ru.susu.algebra.matrix.Matrix;
import ru.susu.algebra.partition.Partition;
import ru.susu.algebra.partition.PartitionsCacheConstructor;
import ru.susu.algebra.partition.PartitionsFacade;
import ru.susu.algebra.partition.PartitionsFactory;
import ru.susu.algebra.partition.PartitionsIterator;
import ru.susu.algebra.partition.comparators.AlternatingByCharDegreeComparator;
import ru.susu.algebra.partition.filter.DifferentOddFilter;
import ru.susu.algebra.partition.filter.ItselfAssociatedPartitionFilter;
import ru.susu.algebra.partition.filter.OnlyFirstAssocPartitionFilter;
import ru.susu.algebra.ui.field.ILegendField;
import ru.susu.algebra.ui.field.SimpleLegendField;
import ru.susu.algebra.util.CollectionUtils;
import ru.susu.algebra.util.LRUHashMap;
import ru.susu.algebra.util.NumberUtilities;

/**
 * Набор методов полезных для таблицы характеров знакопеременных групп
 *
 * @author akargapolov
 * @since: 11.03.2009
 */
public class AlternatingCharTableUtil
{
	/**
	 * Определяет необходимость представления одной строки из Sn двумя в An.
	 * Для этого необходимо выполнение условий:
	 * 1. разбиение самоассоциировано {@link ItselfAssociatedPartitionFilter};
	 * 2. характер в Sn действующий на классе с разбиением образованным из длин на главной диагонали исходного не нулевой;
	 *
	 * @param partition разбиение, представляющее данную строку
	 * @return true, если образуется два характера
	 */
	public static boolean isSpecialRow(Partition partition) throws Exception
	{
		boolean result = true;
		result = result && new ItselfAssociatedPartitionFilter().checkPartition(partition);
		result = result && (partition.getPresentedNumber() > 1);

		Partition hooksPartition = getColumn4SpecialRow(partition);
		IPermCharTableConstructor<BigInteger> contsructor = SymmetricGroupMapTableConstructor.getInstance();
		result = result && !contsructor.getValue(partition, hooksPartition).equals(BigInteger.ZERO);
		return result;
	}

	private static class SpecialRowPredicate implements Predicate<Partition>
	{
		@Override
		public boolean apply(Partition partition)
		{
			try
			{
				return isSpecialRow(partition);
			}
			catch (Exception e)
			{
				throw new RuntimeException(e);
			}
		}
	}

	public static List<Partition> listSpecialRows(int number) throws Exception
	{
		return Lists.newArrayList(Iterators.filter(new PartitionsIterator(number), new SpecialRowPredicate()));
	}

	private static class SpecialRowNotIntegerCharacterPredicate implements Predicate<Partition>
	{
		@Override
		public boolean apply(Partition partition)
		{
			try
			{
				if (!isSpecialRow(partition))
					return false;
				Partition hooksPartition = getColumn4SpecialRow(partition);
				Object value = AlternatingGroupCharTableConstructor.getInstance().getValue(partition, hooksPartition).getValue(0, 0);
				if (!(value instanceof ILegendField<?>))
					return false;
				value = ((ILegendField<?>)value).getValue();
				if (!(value instanceof QuadraticInteger))
					return false;
				return BigInteger.ZERO.compareTo(((QuadraticInteger)value).getD()) < 0;
			}
			catch (Exception e)
			{
				throw new RuntimeException(e);
			}
		}
	}

	public static List<Partition> listSpecialRowsNotIntegerCharacter(int number) throws Exception
	{
		List<Partition> list = Lists.newArrayList(Iterators.filter(new PartitionsIterator(number), new SpecialRowNotIntegerCharacterPredicate()));
		Collections.sort(list, AlternatingByCharDegreeComparator.INSTANCE);
		return list;
	}

	public static Partition getColumn4SpecialRow(Partition rowPartition)
	{
		Integer[] hooks = PartitionsFacade.getMainHookLengths(rowPartition);
		return PartitionsFactory.createPartition(hooks);
	}

	/**
	 * Определяет необходимость представления одного столца в Sn двумя в An.
	 * Для этого необходимо выполнение условий:
	 * 1. разбиение должно состоять из различных нечетных чисел{@link DifferentOddFilter};
	 * 2. характер в Sn соответсвующий разбиению, составленному записыванием элементов исходного на главную диагональ,
	 * действующий на классе с данным разбиением не нулевой;
	 *
	 * @param columnPartition разбиение соответсвующее классу сопряженности;
	 * @return true, если образуется два столбца
	 */
	public static boolean isSpecialColumn(Partition columnPartition) throws Exception
	{
		boolean result = true;
		result = result && new DifferentOddFilter().checkPartition(columnPartition);
		result = result && (columnPartition.getPresentedNumber() > 1);

		if (result)
		{
			IPermCharTableConstructor<BigInteger> contsructor = SymmetricGroupMapTableConstructor.getInstance();
			Partition rowPartition = getRowFromAllOddPartition(columnPartition);
			result = result && !contsructor.getValue(rowPartition, columnPartition).equals(BigInteger.ZERO);
		}

		return result;
	}

	public static QuadraticFieldGeneralForm getSpecialCharacter(Partition specialRow)
	{
		try
		{
			Matrix matrix = AlternatingGroupCharTableConstructor.getInstance().getValue(specialRow, getColumn4SpecialRow(specialRow));
			Object value = matrix.getValue(0, 0);
			if (value instanceof SimpleLegendField<?>)
			{
				return (QuadraticFieldGeneralForm)((SimpleLegendField<QuadraticInteger>)value).getValue();
			}
			throw new RuntimeException();
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static QuadraticField getQuadraticField4SpecialRow(Partition specialRow)
	{
		try
		{
			Matrix matrix = AlternatingGroupCharTableConstructor.getInstance().getValue(specialRow, getColumn4SpecialRow(specialRow));
			Object value = matrix.getValue(0, 0);
			if (value instanceof SimpleLegendField<?>)
			{
				return new QuadraticField(((SimpleLegendField<QuadraticInteger>)value).getValue());
			}
			return new QuadraticField(new QuadraticInteger((BigInteger)value, BigInteger.ZERO,  BigInteger.ZERO));
		}
		catch (Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static Map<QuadraticField, Partition> field2Row(Collection<Partition> specialRows) throws Exception
	{
		return Maps.uniqueIndex(specialRows, new Function<Partition, QuadraticField>()
				{
					@Override
					public QuadraticField apply(Partition from)
					{
						return getQuadraticField4SpecialRow(from);
					}
				});
	}

	public static Map<Partition, QuadraticField> row2Field(Collection<Partition> specialRows) throws Exception
	{
		Map<Partition, QuadraticField> map = Maps.newLinkedHashMap();
		for (Partition p : specialRows)
		{
			map.put(p, getQuadraticField4SpecialRow(p));
		}
		return map;
	}

	private static Map<Partition, BigInteger> _degreeCache = new LRUHashMap<Partition, BigInteger>(1000000);

	/**
	 * Вычисляет степень характера в An {@link HooksDegreeCalculator}
	 * @param partition разбиение, соответствующее характеру;
	 * @return степень характера
	 */
	public static BigInteger calcAlternatingDegree(Partition partition) throws Exception
	{
		if (_degreeCache.containsKey(partition))
		{
			return _degreeCache.get(partition);
		}
		BigInteger res = new HooksDegreeCalculator().calculateDegree(partition);
		if (isSpecialRow(partition))
		{
			if (!res.remainder(BigInteger.valueOf(2)).equals(BigInteger.ZERO))
			{
				throw new RuntimeException();
			}
			res = res.divide(BigInteger.valueOf(2));
		}
		_degreeCache.put(partition, res);
		return res;
	}

	/**
	 * Вычисляет Z[X] = |An| / deg(X)
	 * @param partition разбиение, соответствующее характеру X
	 * @return Z[X]
	 */
	public static BigInteger calcZX(Partition partition)
	{
		try
		{
			BigInteger res = NumberUtilities.getFactorial(partition.getPresentedNumber());
			res = res.divide(new HooksDegreeCalculator().calculateDegree(partition));
			return res;
		}
		catch(Exception e)
		{
			throw new RuntimeException(e);
		}
	}

	public static List<BigInteger> listZXs(List<Partition> partitions) throws Exception
	{
		return Lists.transform(partitions, new Function<Partition, BigInteger>(){
			@Override
			public BigInteger apply(Partition from)
			{
				return calcZX(from);
			}
		});
	}

	/**
	 * Возвращает разбиение, сооставленное записыванием различных нечетных чисел исходного на главную диагональ.
	 *
	 * @param columnPartition разбиение соответствующее классу сопряженных элементов
	 * @return разбиение, соответствующее нужному(всмысле получения двойной строки и столбца) характеру
	 */
	public static Partition getRowFromAllOddPartition(Partition columnPartition)
	{
		Integer[] array = columnPartition.getArrayRepresentation();
		int length = array[array.length - 1] / 2 + 1;
		Integer[] newA = new Integer[length];
		Arrays.fill(newA, Integer.valueOf(0));

		int i, j;
		for (i = 0; i < array.length; i++)
		{
			Integer item = array[array.length - 1 - i];
			newA[i] += (item + 1) / 2;
			for (j = 1; j <= item / 2; j++)
				newA[i + j]++;
		}

		return PartitionsFacade.getEqualsPartitionFromCache(PartitionsFactory.createPartition(newA));
	}

	public static List<Partition> getAscendingDegreeFirstAssociatedPartitions(int number)
	{
		return CollectionUtils.sort(new PartitionsCacheConstructor().getAscendingPartitions(number, new OnlyFirstAssocPartitionFilter()),
				true, AlternatingByCharDegreeComparator.INSTANCE);
	}

	public static LinkedHashMap<Partition, Integer> indexRowNumbers(List<Partition> partitions)
	{
		if (partitions.isEmpty())
		{
			return Maps.newLinkedHashMap();
		}
		int number = partitions.iterator().next().getPresentedNumber();
		List<Partition> allPartitions = getAscendingDegreeFirstAssociatedPartitions(number);
		List<Partition> sortedPartitions = CollectionUtils.sort(partitions, true, AlternatingByCharDegreeComparator.INSTANCE);
		LinkedHashMap<Partition, Integer> index = Maps.newLinkedHashMap();
		int index1 = 0, index2 = 0, count = 0;
		while (index2 < sortedPartitions.size())
		{
			Partition p1 = allPartitions.get(index1);
			Partition p2 = sortedPartitions.get(index2);
			if (p1.equals(p2))
			{
				index.put(p2, count + 1);
				index2++;
			}
			if (new SpecialRowPredicate().apply(p1))
				count+=2;
			else
				count++;
			index1++;
		}

		return index;
	}
}
